package filewriting;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Random;

import commonfunctions.Functions;
import commonfunctions.GetStats;
import commonfunctions.Matrix;
import guiobjects.Observation;

/**
 * Creates a display that shows, at a glance, the fitness of a track. Works only when IDs are known.
 * 
 * Vertical distance determines the ID, horizontal determines the time. Greyscale lines are weights that
 * weren't chosen, red lines are weights that were.
 * 
 * Thus, a lot of horizontal red lines is good, especially with lots of black lines; this means that an 
 * object was tracked correctly despite lots of noise. 
 * 
 * @author Trevor Terris
 *
 */
public class FitnessViewWriter 
{
	private static Random myRandom = new Random();
	public static final String FITNESS_VIEW_NAME = "fitness-view.txt";

	public static Thread startWriting(ArrayList<Observation> observations, Matrix weights, Matrix tracks, String dirName)
	{
		Thread toReturn = new WritingThread(observations, weights, tracks, dirName);
		toReturn.start();
		return toReturn;
	}
	
	private static class WritingThread extends Thread
	{
		private ArrayList<Observation> myObservations;
		private Matrix myWeights;
		private Matrix myTracks;
		private String myDirName;
		public WritingThread(ArrayList<Observation> obs, Matrix weights, Matrix tracks, String dirName)
		{
			myObservations = obs;
			myWeights = weights;
			myTracks = tracks;
			myDirName = dirName;
		}
		public void run()
		{
			int[] stats = GetStats.print(myObservations, myWeights, myTracks);
			try
			{
				int falsePos = stats[0];
				int falseNeg = stats[1];
				int biggestID = 0;
				int maxFrame = myObservations.get(myObservations.size() - 1).frameNum;
				ArrayList<Integer> seenOnce = new ArrayList<Integer>();
				ArrayList<Integer> seenTwice = new ArrayList<Integer>();
				for (int k = myObservations.size() - 1; k >= 0; k--)
				{
					int thisID = myObservations.get(k).id;
					if (thisID > biggestID)
						biggestID = thisID;
					if (!seenOnce.contains(thisID))
						seenOnce.add(thisID);
					else if (!seenTwice.contains(thisID))
						seenTwice.add(thisID);
					else break;
				}
				Matrix trackLinks = new Matrix(biggestID + 1);
				for (int i = 0; i < myTracks.length(); i++)
				{
					for (int j = i + 1; j < myTracks.length(); j++)
					{
						int firstID = Math.min(myObservations.get(i).id, myObservations.get(j).id);
						int secondID = Math.max(myObservations.get(i).id, myObservations.get(j).id);
						if (firstID != secondID && myTracks.getBoolean(i, j))
							trackLinks.set(trackLinks.get(firstID, secondID) + 1, firstID, secondID);
					}
				}
				ArrayList<ArrayList<Integer>> connectedTracks = new ArrayList<ArrayList<Integer>>();
				ArrayList<Integer> added = new ArrayList<Integer>();
				for (int i = 0; i < trackLinks.length(); i++)
				{
					if (!added.contains(i))
					{
						ArrayList<Integer> thisTrack = new ArrayList<Integer>();
						ArrayList<Integer> toCheck = new ArrayList<Integer>();
						toCheck.add(i);
						while (toCheck.size() > 0)
						{
							int thisID = toCheck.remove(0);
							if (!added.contains(thisID))
							{
								added.add(thisID);
								thisTrack.add(thisID);
								double[] col = trackLinks.singleCol(thisID);
								double[] row = trackLinks.singleRow(thisID);
								for (int j = 0; j < col.length; j++)
								{
									if (col[j] > 0.0 || row[j] > 0.0)
										toCheck.add(j);
								}
							}
						}
						connectedTracks.add(thisTrack);
					}
				}
				ArrayList<ArrayList<Integer>> orderedConnectedTracks = new ArrayList<ArrayList<Integer>>();
				for (int i = 0; i < connectedTracks.size(); i++)
				{
					ArrayList<Integer> thisTrack = connectedTracks.get(i);
					ArrayList<Integer> thisOrderedTrack = new ArrayList<Integer>();
					if (thisTrack.size() > 1)
					{
						int biggestX = 0;
						int biggestY = 0;
						double biggestConnection = 0.0;
						for (int j = 0; j < thisTrack.size(); j++)
						{
							for (int k = 0; k < thisTrack.size(); k++)
							{
								if (trackLinks.get(thisTrack.get(j), thisTrack.get(k)) > biggestConnection)
								{
									biggestX = j;
									biggestY = k;
									biggestConnection = trackLinks.get(thisTrack.get(j), thisTrack.get(k));
								}
							}
						}
						thisOrderedTrack.add(thisTrack.remove(Math.min(biggestX, biggestY)));
						thisOrderedTrack.add(thisTrack.remove(Math.max(biggestX, biggestY) - 1));
						while (thisTrack.size() > 0)
						{
							boolean isAddedToFirst = false;
							int biggestIndex = 0; 
							double biggestVal = 0;
							for (int j = 0; j < thisTrack.size(); j++)
							{
								int first = thisOrderedTrack.get(0);
								int last = thisOrderedTrack.get(thisOrderedTrack.size() - 1);
								if (trackLinks.get(Math.min(first, thisTrack.get(j)), Math.max(first, thisTrack.get(j))) >= biggestVal)
								{
									biggestIndex = j;
									biggestVal = trackLinks.get(Math.min(first, thisTrack.get(j)), Math.max(first, thisTrack.get(j)));
									isAddedToFirst = true;
								}
								if (trackLinks.get(Math.min(last, thisTrack.get(j)), Math.max(last, thisTrack.get(j))) >= biggestVal)
								{
									biggestIndex = j;
									biggestVal = trackLinks.get(Math.min(last, thisTrack.get(j)), Math.max(last, thisTrack.get(j)));
									isAddedToFirst = false;
								}
							}
							if (isAddedToFirst)
								thisOrderedTrack.add(0, thisTrack.remove(biggestIndex));
							else
								thisOrderedTrack.add(thisTrack.remove(biggestIndex));
						}
					}
					else
						thisOrderedTrack = thisTrack;
					orderedConnectedTracks.add(thisOrderedTrack);
				}
				int[] idMappings = new int[biggestID + 1];
				int offset = 0;
				for (int i = 0; i < orderedConnectedTracks.size(); i++)
				{
					for (int j = 0; j < orderedConnectedTracks.get(i).size(); j++)
						idMappings[orderedConnectedTracks.get(i).get(j)] = j + offset;
					offset += orderedConnectedTracks.get(i).size();
				}
				File f = new File(myDirName + "/" + FITNESS_VIEW_NAME);
		        Writer output = null;
		        output = new BufferedWriter(new FileWriter(f));
		        output.flush();
		        //first it draws the lines that are determined by the weights. Everything else is written
		        //on top of them.
				for (int k = 0; k < myObservations.size(); k ++)
				{
					for (int j = 0; j < myObservations.size(); j++)
					{
						if (myWeights.get(k, j) > 0.0 && !myTracks.getBoolean(k, j))
						{
							int greyscale = 50 + (int)(150 * myRandom.nextDouble());
							
							double xStart = 0.01 + 0.98 * ((double)myObservations.get(k).frameNum)/((double)maxFrame);
							double xEnd = 0.01 + 0.98 * ((double)myObservations.get(j).frameNum)/((double)maxFrame);
							double yStart = 0.01 + 0.98 *((double)idMappings[myObservations.get(k).id])/((double)(biggestID + 1));
							double yEnd = 0.01 + 0.98 *((double)idMappings[myObservations.get(j).id])/((double)(biggestID + 1));
							int width = (int)(myWeights.get(k, j) / 0.2) + 1;
							output.write("Line 0 " + xStart + " " + yStart + " " + xEnd + " " + yEnd + " " + greyscale + " " + greyscale + " " + greyscale +" " + width);
							output.write("\n");
						}
					}
				}
				
				//next it draws the tracked edges. Same as the weights, but there are much fewer, and they're red
				for (int k = 0; k < myTracks.length(); k++)
				{
					for (int j = 0; j < myTracks.length(); j++)
					{
						if (myTracks.getBoolean(k,j))
						{
							String toWrite = "Line 0 ";
							int width = (int)(myWeights.get(k, j) / 0.2) + 1;
							double xStart = 0.01 + 0.98 * ((double)myObservations.get(k).frameNum)/((double)maxFrame);
							double yStart = 0.01 + 0.98 * ((double)idMappings[myObservations.get(k).id])/((double)(biggestID + 1));
							double xEnd = 0.01 + 0.98 * ((double)myObservations.get(j).frameNum)/((double)maxFrame);
							double yEnd = 0.01 + 0.98 * ((double)idMappings[myObservations.get(j).id])/((double)(biggestID + 1));
							boolean isToDummy = myObservations.get(k).isEntranceNode || myObservations.get(k).isExitNode ||
												myObservations.get(j).isEntranceNode || myObservations.get(j).isExitNode;
							toWrite += xStart + " " + yStart +" " + xEnd + " " + yEnd + " " + (isToDummy?150:255) + " 0 0 " + width + "\n";
							output.write(toWrite);
						}
					}
				}
				
				ArrayList<Integer> trackStarts = Functions.trackStarts(myTracks);
				ArrayList<Integer> trackEnds = Functions.trackEnds(myTracks);
				//draws little circles at the beginning and end of each path, to make single points more visible
				for (int k = 0; k < myObservations.size(); k++)
				{
					if (trackStarts.contains(k) || trackEnds.contains(k))
					{
						String toWrite = "Circle 0 " ;
						toWrite += 0.01 + 0.98 * ((double)myObservations.get(k).frameNum)/((double)maxFrame) + " ";
						toWrite += 0.01 + 0.98 * ((double)idMappings[myObservations.get(k).id])/((double)(biggestID + 1)) + " 2 255 0 0";
						output.write(toWrite + "\n");
					}
				}
				
				double weightSum = (double)((int)(Functions.getPathSums(myWeights, myTracks) * 1000)) / 1000;
				
				//finally, writes the number of false positives and false negatives. Intuitively, the false 
				//positives are when there are lines that aren't horizontal, and false negatives are gaps
				//in a segment of horizontal lines.
				output.write("Text 0 0.0 1.0 False Positives: " + falsePos + ", False Negatives: " + falseNeg + 
						", Track weight sum: " + weightSum);
				
				int[] startingFrame = new int[biggestID + 1];
				for (int i = 0; i < startingFrame.length; i++)
					startingFrame[i] = maxFrame;
				
				for (int i = 0; i <= biggestID; i++)
				{
					for (int j = 0; j < myObservations.size(); j++)
						startingFrame[i] = (startingFrame[i] > myObservations.get(j).frameNum && myObservations.get(j).id == i)?
											myObservations.get(j).frameNum:startingFrame[i];
				}
				
				int[] minStartingFrameAfter = new int[biggestID + 1];
				
				for (int i = 0; i < startingFrame.length; i++)
				{
					int earliestFrame = -1;
					for (int j = 0; j < startingFrame.length; j++)
					{
						if ((startingFrame[j] < earliestFrame || earliestFrame == -1) && idMappings[i] <= idMappings[j])
							earliestFrame = startingFrame[j];
					}
					minStartingFrameAfter[i] = earliestFrame;
				}
				
				for (int i = 0; i <= biggestID; i++)
				{
					if (minStartingFrameAfter[i] <= 3)
						output.write("\nText 0 middle 0.0 " + (0.01 + 0.98 * ((double)idMappings[i])/((double)(biggestID + 1))) + " " + i);
					else
						output.write("\nText 0 middle "+ ((0.98 * ((double)(minStartingFrameAfter[i] - 3))/((double)maxFrame))) +" " + (0.01 + 0.98 * ((double)idMappings[i])/((double)(biggestID + 1))) + " " + i);
				}
				
				for (int l = 1; l <= maxFrame; l++)
				{
					double lineX = 0.01 + 0.98 * ((double)l)/((double)maxFrame);
					output.write("\nLine " + l + " " + lineX + " 0.0 " + lineX + " 1.0 0 0 255 1");
					output.write("\nText " + l + " top " + lineX + " 0.0 " + l);
				}
				
				output.close();
			}
			catch (Exception e)
			{
				e.printStackTrace();
				System.out.println("Error.");
			}
		}
	}
}
